## ---- imports ----

## importing libraries required for the WS server;
## the only dependency you have to install manually
## is 'websocket' module (you can probably do that
## by running 'pip install websockets' in terminal)

## Note: if you are using hyperbola like me,
## you won't find python-pip in repositories
## as it is not considered free software.
## But you can use pacman to install python packages
## instead: for this project you can simply run
## $ doas pacman -Sy python-websockets
## to install the websockets package

import websockets
import asyncio
import codecs
import time
import json
import sys


## ---- reading settings from every possible place ----

## trying to read the config file (otherwise setting
## everything to default)

try:
    ## trying to read config file to get settings
    settings = codecs.open("config.json", "r", encoding = "UTF-8").read()
except:
    ## using defaults if there is no file or it is
    ## unaccessible for whatever reason
    settings = {
        "port": 6392,
        "visible_message_history_length": 100,
    }

## also reading arguments given from the cli (argv)
## and overwriting settings from the file (or defauts)

possible_arguments_list_shorts = {
    "p": "port",
    "h": "visible_message_history_length",
}

possible_arguments_list_longs = {
    "port": "port",
    "history-length": "visible_message_history_length",
}

current_arg = ""

for arg in sys.argv[1:]:
    if (arg == "--help"):
        print( codecs.open("src/help.txt", "r", encoding = "UTF-8").read() )
        sys.exit(0)

    elif (arg == "--version"):
        print("Version: 0.1 (developer_preview)")
        sys.exit(0)
    
    elif arg[:2] == "--":
        if arg[2:] in possible_arguments_list_longs:
            current_arg = possible_arguments_list_longs[arg[2:]]

        else:
            print("[ERROR] Unrecognized argument: {}\nFor usage info and possible options see '{} --help'".format(arg, sys.argv[0]))
            sys.exit(1)

    elif arg[:1] == "-":
        if arg[1:] in possible_arguments_list_shorts:
            current_arg = possible_arguments_list_shorts[arg[1:]]

        else:
            print("[ERROR] Unrecognized argument: {}\nFor usage info and possible options see '{} --help'".format(arg, sys.argv[0]))
            sys.exit(1)

    else:
        if current_arg != "":
            settings[current_arg] = arg
            current_arg = ""
        else:
            print("[ERROR] Positional argument '{}' detected, but none expected\nFor usage info and possible options see '{} --help'".format(arg, sys.argv[0]))
            sys.exit(1)

## certain settings values have to be converted
## into proper data types; doing it there

settings["visible_message_history_length"] = int(settings["visible_message_history_length"])


## ---- defining useful server functions ----
## place for future code


## ---- actual server code ----

## sent messages array
messages = []

## defining client handler for websockets
async def client_handler(websocket, requested_path):
    ## client tells us if it is
    ## a listener or a sender
    responce = await websocket.recv()
    client_role = json.loads(responce)

    ## listener code
    if client_role == "listener_basic":
        ## remembering last message ID to send only new messages
        last_message_id = len(messages) - settings["visible_message_history_length"]

        ## anti infinite loop patch
        if last_message_id < 0:
            last_message_id = 0

        ## using "try" for safety
        try:
            ## always try to send new messages
            while True:
                new_messages_amount = ((len(messages)) - last_message_id)
                
                if new_messages_amount > 0:
                    last_message_id += new_messages_amount

                    print("new_messages_amount = {}".format(new_messages_amount))

                    if new_messages_amount == 1:
                        print("pass 1")
                        msg = messages[-1]
                        await websocket.send( json.dumps(msg) )

                    else:
                        print("pass 2")
                        msg = messages[-new_messages_amount:]
                        for i in msg:
                            print("pass 3")
                            await websocket.send( json.dumps(i) )
                            await asyncio.sleep(0.2)

                else:
                    await asyncio.sleep(1)

        ## closing connection if anything goes wrong
        except Exception as e:
            print("[client_handler] Warning: got exception \"{}\", closing connection...".format(e))
            return 1

    ## sender code
    elif client_role == "sender_basic":
        try:
            ## always listening for messages
            while True:
                new_message = await websocket.recv()

                ## "sanity check"
                try:
                    msg = json.loads(new_message)

                    ## checking if all required objects are present
                    if not ( ("data" in msg) and ("nickname" in msg["data"]) and ("content" in msg["data"]) ):
                        print("[client_handler:sender_basic:sanity_check] Warning: message is not formatted properly, closing connection...")
                        return 1
                except Exception as e:
                    print("[client_handler:sender_basic:sanity_check] Warning: unexpected exception \"{}\", closing connection...".format(e))
                    return 1

                ## add server timestamp to the message                        
                msg["timestamp_server"]  = time.time()

                ## add message to global list
                messages.append(msg)

        ## closing connection on exceptions
        except Exception as e:
            print("[client_handler:sender_basic] Warning: unexpected exception \"{}\", closing connection...".format(e))
            

    ## getting rid of unknown client types
    else:
        print("[client_handler] Warning: client tried to connect as \"{}\", closing connection...".format(client_role))
        return 0

## starting websocket server
websocket_server = websockets.serve(client_handler, "", settings["port"])

asyncio.get_event_loop().run_until_complete(websocket_server)
asyncio.get_event_loop().run_forever()
